
#include <string>
#include <vector>

#include <ft2build.h>
#include FT_FREETYPE_H

#include "../defines.hpp"
#include "../math.hpp"
#include "../debug.hpp"

#include "Font.hpp"
#include "FontManager.hpp"

namespace zen {

	namespace video {

		FontManager::FontManager(TextureManager* textureManager) {
			this->textureManager = textureManager;
			fontLibrary = nullptr;
			if (textureManager != nullptr)
				FT_Init_FreeType(&fontLibrary);
		}

		FontManager::~FontManager() {
			if (fontLibrary != nullptr)
				FT_Done_FreeType(fontLibrary);
		}

		glm::ivec4 FontManager::findGlyphPosition(int &nextY, int &posX, int &posY, const int &width, const int &height, const int &maxWidth, const int &maxHeight) {
			// TODO: implement an algorithm for better glyph placement on texture
			if (width <= 0 && height <= 0)
				return glm::ivec4(-1, 0, 0, 0);
			int nY = nextY, pX = posX, pY = posY;
			if (posX + width > maxWidth) {
				if (nextY + height >= maxHeight) {
					return glm::ivec4(-1, 0, 0, 0);
				} else {
					posX = width + 2;
					posY = nextY;
					nextY += height + 1;
					return glm::ivec4(1, nY, width, height);
				}
			} else {
				posX += width + 1;
				nextY = glm::max(nextY, posY + height + 1);
				return glm::ivec4(pX, pY, width, height);
			}
		}

		bool FontManager::loadFontFace(const std::string &id, const std::string &fileName, int index) {
			zen_call("FontManager.loadFontFace(\"" + id + "\", \"" + fileName + "\", \"" + to_str(index) + "\")");
			FontFacesIterator iterator = fontFaces.find(id);
			if (iterator != fontFaces.end())
				return true;
			FT_Face fontFace;
			int error = FT_New_Face(fontLibrary, fileName.c_str(), index, &fontFace);
			if (error) {
				zen_if_log(error == FT_Err_Unknown_File_Format, "Error: Unsupported font face format.");
				zen_else_log("Error: Could not load font face.");
				return false;
			}
			fontFaces.insert(FontFacesEntry(id, fontFace));
			return true;
		}

		bool FontManager::freeFontFace(const std::string &id) {
			FontFacesIterator iterator = fontFaces.find(id);
			if (iterator != fontFaces.end()) {
				if (iterator->second != nullptr)
					FT_Done_Face(iterator->second);
				fontFaces.erase(iterator);
				return true;
			}
			return false;
		}

		void FontManager::freeFontFaces() {
			for (FontFacesIterator iterator = fontFaces.begin(); iterator != fontFaces.end(); iterator++)
				if (iterator->second != nullptr)
					FT_Done_Face(iterator->second);
			fontFaces.clear();
		}

		PFont FontManager::createFont(const std::string &id, int size, FT_Face fontFace) {
			zen_call("FontManager.createFont(\"" + id + "\", \"" + to_str(size) + "\")");
			FT_Set_Char_Size(fontFace, 0, size << 6, 96, 96);
			int textureSize = glm::nextPowerOfTwo(16 * size);
			Pixmap pixmap(textureSize, textureSize, 1, GL_RGBA);
			pixmap.fill(0, 0, 0, 0);
			GLubyte* data = pixmap.getData();
			glm::ivec4* rects = new glm::ivec4[128];
			glm::ivec3* steps = new glm::ivec3[128];
			int posX = 1;
			int posY = 1;
			int nextY = 0;
			for (int ch = 0; ch < 128; ch++) {
				int charIndex = FT_Get_Char_Index(fontFace, ch);
				if (charIndex > 0 && FT_Load_Glyph(fontFace, charIndex, FT_LOAD_DEFAULT) == 0) {
					FT_GlyphSlot& glyph = fontFace->glyph;
					if (glyph->format != FT_GLYPH_FORMAT_BITMAP)
						FT_Render_Glyph(glyph, FT_RENDER_MODE_NORMAL);
					FT_Bitmap& bitmap = glyph->bitmap;
					rects[ch] = findGlyphPosition(nextY, posX, posY, bitmap.pitch, bitmap.rows, textureSize, textureSize);
					steps[ch] = glm::ivec3(glyph->advance.x >> 6, glyph->bitmap_left, glyph->bitmap_top);
					glm::ivec4& rect = rects[ch];
					if (rect[0] >= 0) {
						for (int j = 0; j < bitmap.rows; j++) {
							for (int i = 0; i < bitmap.pitch; i++) {
								int offset = 4 * (rect[0] + i + (rect[1] + j) * textureSize);
								data[offset] = data[offset + 1] = data[offset + 2] = 255;
								data[offset + 3] = bitmap.buffer[i + bitmap.width * j];
							}
						}
					}
				} else {
					rects[ch] = glm::ivec4(-1, 0, 0, 0);
				}
			}
			PTexture texture = textureManager->loadPixmap("mainFont", pixmap);
			arr(glm::ivec3) stepsPtr(steps);
			arr(glm::ivec4) rectsPtr(rects);
			PFont font(new Font(texture, textureSize, stepsPtr, rectsPtr, 128));
			fonts.insert(FontEntry(FontKey(size, id), font));
			return font;
		}

	} // namespace video

} // namespace zen
